﻿#region License

// Another free, open solution by folks who get it.
// Copyright © 2011 Business in Unison, Inc.
// 
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
// 
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
// GNU General Public License for more details.
// 
// You should have received a copy of the GNU General Public License
// along with this program.  If not, see <http://www.gnu.org/licenses/>.

#endregion

using System;
using System.Collections.Generic;
using System.Linq;

using Patterns.Collections;

namespace Patterns.Text
{
	/// <summary>
	/// 	Manages common text transformations for single words.  Capable of formatting, pluralizing, and singularizing words.
	/// </summary>
	public class Inflector : IInflector
	{
		/// <summary>
		/// 	The pluralizer rules.
		/// </summary>
		protected IList<TextRule> _pluralizerRules;

		/// <summary>
		/// 	The singularizer rules.
		/// </summary>
		protected IList<TextRule> _singularizerRules;

		/// <summary>
		/// 	The uncountable strings.
		/// </summary>
		protected IList<string> _uncountables;

		/// <summary>
		/// 	Initializes a new instance of the <see cref = "Inflector" /> class.
		/// </summary>
		public Inflector()
		{
			Initialize();
		}

		#region IInflector Members

		/// <summary>
		/// 	Gets the proper item form.
		/// </summary>
		/// <param name = "item">The item.</param>
		/// <param name = "itemCount">The item count.</param>
		/// <returns></returns>
		public string GetProperItemForm(string item, int itemCount)
		{
			return Math.Abs(itemCount) == 1 ? GetSingularForm(item) : GetPluralForm(item);
		}

		/// <summary>
		/// 	Gets the singular form of the specified text.
		/// </summary>
		/// <param name = "text">The text.</param>
		/// <returns></returns>
		public string GetSingularForm(string text)
		{
			return ApplyFormRules(text, _singularizerRules);
		}

		/// <summary>
		/// 	Gets the plural form of the specified text.
		/// </summary>
		/// <param name = "text">The text.</param>
		/// <returns></returns>
		public string GetPluralForm(string text)
		{
			return ApplyFormRules(text, _pluralizerRules);
		}

		#endregion

		/// <summary>
		/// 	Applies the form rules.
		/// </summary>
		/// <param name = "text">The text.</param>
		/// <param name = "pluralizerRules">The pluralizer rules.</param>
		/// <returns></returns>
		protected virtual string ApplyFormRules(string text, IEnumerable<TextRule> pluralizerRules)
		{
			string targetText = text.Trim();
			if (string.IsNullOrEmpty(targetText) || IsUncountable(targetText))
				return targetText;

			IEnumerable<string> results = from rule in pluralizerRules
				let result = rule.Apply(targetText)
				where !string.IsNullOrEmpty(result)
				select result;

			return results.FirstOrDefault() ?? text;
		}

		/// <summary>
		/// 	Initializes this instance.
		/// </summary>
		protected virtual void Initialize()
		{
			InitializePlurals();
			InitializeSingulars();
			InitializeIrregulars();
			_uncountables = new List<string> { "equipment", "information", "rice", "money", "species", "series", "fish", "sheep" };
		}

		/// <summary>
		/// 	Initializes the plural rules.
		/// </summary>
		protected virtual void InitializePlurals()
		{
			_pluralizerRules = new List<TextRule>
			{
				new TextRule("(oxen|octopi|viri|aliases|quizzes)$", "$1"),
				new TextRule("(people|men|children|sexes|moves|stadiums)$", "$1"),
				new TextRule("(quiz)$", "$1zes"),
				new TextRule("^(ox)$", "$1en"),
				new TextRule("([m|l])ice$", "$1ice"),
				new TextRule("([m|l])ouse$", "$1ice"),
				new TextRule("(matr|vert|ind)ix|ex$", "$1ices"),
				new TextRule("(x|ch|ss|sh)$", "$1es"),
				new TextRule("([^aeiouy]|qu)y$", "$1ies"),
				new TextRule("(hive)$", "$1s"),
				new TextRule("(?:([^f])fe|([lr])f)$", "$1$2ves"),
				new TextRule("sis$", "ses"),
				new TextRule("([ti])a$", "$1a"),
				new TextRule("([ti])um$", "$1a"),
				new TextRule("(buffal|tomat)o$", "$1oes"),
				new TextRule("(bu)s$", "$1ses"),
				new TextRule("(alias|status)$", "$1es"),
				new TextRule("(octop|vir)i$", "$1i"),
				new TextRule("(octop|vir)us$", "$1i"),
				new TextRule("(ax|test)is$", "$1es"),
				new TextRule("s$", "s"),
				new TextRule("$", "s")
			};
		}

		/// <summary>
		/// 	Initializes the singular rules.
		/// </summary>
		protected virtual void InitializeSingulars()
		{
			_singularizerRules = new List<TextRule>
			{
				new TextRule("(quiz)zes$", "$1"),
				new TextRule("(matr)ices$", "$1ix"),
				new TextRule("(vert|ind)ices$", "$1ex"),
				new TextRule("^(ox)en", "$1"),
				new TextRule("(alias|status)$", "$1"),
				new TextRule("(alias|status)es$", "$1"),
				new TextRule("(octop|vir)us$", "$1us"),
				new TextRule("(octop|vir)i$", "$1us"),
				new TextRule("(cris|ax|test)es$", "$1is"),
				new TextRule("(cris|ax|test)is$", "$1is"),
				new TextRule("(shoe)s$", "$1"),
				new TextRule("(o)es$", "$1"),
				new TextRule("(bus)es$", "$1"),
				new TextRule("([m|l])ice$", "$1ouse"),
				new TextRule("(x|ch|ss|sh)es$", "$1"),
				new TextRule("(m)ovies$", "$1ovie"),
				new TextRule("(s)eries$", "$1eries"),
				new TextRule("([lr])ves$", "$1f"),
				new TextRule("(tive)s$", "$1"),
				new TextRule("(hive)s$", "$1"),
				new TextRule("([^f])ves$", "$1fe"),
				new TextRule("(^analy)sis$", "$1sis"),
				new TextRule("(^analy)ses$", "$1sis"),
				new TextRule("((a)naly|(b)a|(d)iagno|(p)arenthe|(p)rogno|(s)ynop|(t)he)ses$", "$1$2sis"),
				new TextRule("([ti])a$", "$1um"),
				new TextRule("(n)ews$", "$1ews"),
				new TextRule("(s|si|u)s$", "$1s"),
				new TextRule("s$", "")
			};
		}

		/// <summary>
		/// 	Initializes the irregular rules.
		/// </summary>
		protected virtual void InitializeIrregulars()
		{
			AddIrregular("person", "people");
			AddIrregular("man", "men");
			AddIrregular("child", "children");
			AddIrregular("sex", "sexes");
			AddIrregular("move", "moves");
			AddIrregular("stadium", "stadiums");
		}

		/// <summary>
		/// 	Adds an irregular handler.
		/// </summary>
		/// <param name = "singular">The singular form.</param>
		/// <param name = "plural">The plural form.</param>
		protected virtual void AddIrregular(string singular, string plural)
		{
			string singularRemainder = singular.Length > 1 ? singular.Substring(1) : string.Empty;
			string pluralRemainder = plural.Length > 1 ? plural.Substring(1) : string.Empty;
			const string ruleFormat = "({0}){1}$";
			const string replacementFormat = "$1{0}";
			_pluralizerRules.Insert(0, new TextRule(string.Format(ruleFormat, singular.First(), singularRemainder), string.Format(replacementFormat, pluralRemainder)));
			_singularizerRules.Insert(0, new TextRule(string.Format(ruleFormat, plural.First(), pluralRemainder), string.Format(replacementFormat, singularRemainder)));
		}

		/// <summary>
		/// 	Adds the uncountable terms.
		/// </summary>
		/// <param name = "terms">The terms.</param>
		protected virtual void AddUncountable(params string[] terms)
		{
			_uncountables.AddRange(terms.Select(term => term.Trim()));
		}

		/// <summary>
		/// 	Determines whether the specified term is uncountable.
		/// </summary>
		/// <param name = "term">The term.</param>
		/// <returns>
		/// 	<c>true</c> if the specified term is uncountable; otherwise, <c>false</c>.
		/// </returns>
		protected virtual bool IsUncountable(string term)
		{
			return _uncountables.Any(item => StringComparer.OrdinalIgnoreCase.Equals(item, term));
		}
	}
}